package org.hamster.common.aggrid;

import org.hamster.common.aggrid.model.FilterModel;
import org.hamster.common.aggrid.model.GetRowsRequest;
import org.hamster.common.aggrid.model.GetRowsResponse;
import org.hamster.common.aggrid.model.SortModel;

import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Stream;

/**
 * Service class for handling ag-Grid requests with infinite scroll, filtering and sorting
 * 
 * @param <T> The type of data being handled
 */
public class AgGridService<T> {
    
    /**
     * Process the get rows request from ag-Grid and return appropriate response
     * 
     * @param request The request from ag-Grid
     * @param allData The complete dataset
     * @param filterFunction Function to apply filters to the data
     * @param sortFunction Function to sort the data
     * @return Response with data for the requested block
     */
    public GetRowsResponse getRows(GetRowsRequest request, 
                                   List<T> allData,
                                   Predicate<T> filterFunction,
                                   java.util.Comparator<T> sortFunction) {
        
        // Apply filtering
        Stream<T> filteredStream = allData.stream();
        if (filterFunction != null) {
            filteredStream = filteredStream.filter(filterFunction);
        }
        
        // Apply sorting
        if (sortFunction != null) {
            filteredStream = filteredStream.sorted(sortFunction);
        }
        
        List<T> filteredAndSortedData = filteredStream.toList();
        
        // Calculate response parameters
        int totalRows = filteredAndSortedData.size();
        int startRow = request.getStartRow();
        int endRow = Math.min(request.getEndRow(), totalRows);
        
        // Extract the block of data for this request
        List<T> rowsThisBlock;
        if (startRow < totalRows) {
            rowsThisBlock = filteredAndSortedData.subList(startRow, endRow);
        } else {
            rowsThisBlock = List.of();
        }
        
        // Determine if this is the last row
        boolean lastRow = endRow >= totalRows;
        
        return new GetRowsResponse(rowsThisBlock, totalRows, lastRow);
    }
    
    /**
     * Create a filter predicate based on ag-Grid filter model
     * 
     * @param filterModel The filter model from ag-Grid
     * @param fieldExtractor Function to extract field value from data object
     * @return Predicate for filtering data
     */
    public <V> Predicate<T> createFilterPredicate(Map<String, FilterModel> filterModel, 
                                                  java.util.function.Function<T, V> fieldExtractor,
                                                  String fieldName) {
        if (filterModel == null || !filterModel.containsKey(fieldName)) {
            return t -> true; // No filter for this field
        }
        
        FilterModel model = filterModel.get(fieldName);
        return createFilterPredicate(model, fieldExtractor);
    }
    
    /**
     * Create a filter predicate based on a single FilterModel
     * 
     * @param model The filter model
     * @param fieldExtractor Function to extract field value from data object
     * @return Predicate for filtering data
     */
    public <V> Predicate<T> createFilterPredicate(FilterModel model,
                                                  java.util.function.Function<T, V> fieldExtractor) {
        // Handle composite filters (AND/OR conditions)
        if (model.getConditions() != null && !model.getConditions().isEmpty()) {
            return createCompositeFilterPredicate(model, fieldExtractor);
        }
        
        // Handle simple filter
        String filterType = model.getFilterType();
        String type = model.getType();
        Object filterValue = model.getFilter();
        
        return t -> {
            V value = fieldExtractor.apply(t);
            return testFilter(value, filterType, type, filterValue);
        };
    }
    
    /**
     * Create a filter predicate for composite filters (with AND/OR conditions)
     * 
     * @param model The filter model with conditions
     * @param fieldExtractor Function to extract field value from data object
     * @return Predicate for filtering data
     */
    private <V> Predicate<T> createCompositeFilterPredicate(FilterModel model,
                                                            java.util.function.Function<T, V> fieldExtractor) {
        List<FilterModel> conditions = model.getConditions();
        String operator = model.getOperatorComposite();
        
        if (conditions.isEmpty()) {
            return t -> true;
        }
        
        Predicate<T> result = createFilterPredicate(conditions.get(0), fieldExtractor);
        
        for (int i = 1; i < conditions.size(); i++) {
            Predicate<T> nextPredicate = createFilterPredicate(conditions.get(i), fieldExtractor);
            if ("OR".equalsIgnoreCase(operator)) {
                result = result.or(nextPredicate);
            } else {
                // Default to AND
                result = result.and(nextPredicate);
            }
        }
        
        return result;
    }
    
    /**
     * Test if a value matches the filter criteria
     * 
     * @param value The value to test
     * @param filterType The type of filter
     * @param type The comparison type
     * @param filterValue The filter value
     * @return true if the value matches the filter, false otherwise
     */
    private <V> boolean testFilter(V value, String filterType, String type, Object filterValue) {
        if (value == null) {
            return "empty".equals(type);
        }
        
        switch (filterType) {
            case "text":
                return testTextFilter(value.toString(), type, filterValue.toString());
            case "number":
                return testNumberFilter(value, type, filterValue);
            case "date":
                return testDateFilter(value, type, filterValue);
            default:
                return true;
        }
    }
    
    /**
     * Test text filter
     */
    private boolean testTextFilter(String value, String type, String filter) {
        if (filter == null) return true;
        
        switch (type) {
            case "equals":
                return value.equals(filter);
            case "notEqual":
                return !value.equals(filter);
            case "contains":
                return value.contains(filter);
            case "notContains":
                return !value.contains(filter);
            case "startsWith":
                return value.startsWith(filter);
            case "endsWith":
                return value.endsWith(filter);
            default:
                return true;
        }
    }
    
    /**
     * Test number filter
     */
    @SuppressWarnings("unchecked")
    private <V> boolean testNumberFilter(V value, String type, Object filter) {
        if (!(value instanceof Number) || !(filter instanceof Number)) {
            return true;
        }
        
        double valueDouble = ((Number) value).doubleValue();
        double filterDouble = ((Number) filter).doubleValue();
        
        switch (type) {
            case "equals":
                return valueDouble == filterDouble;
            case "notEqual":
                return valueDouble != filterDouble;
            case "greaterThan":
                return valueDouble > filterDouble;
            case "greaterThanOrEqual":
                return valueDouble >= filterDouble;
            case "lessThan":
                return valueDouble < filterDouble;
            case "lessThanOrEqual":
                return valueDouble <= filterDouble;
            default:
                return true;
        }
    }
    
    /**
     * Test date filter
     */
    private <V> boolean testDateFilter(V value, String type, Object filter) {
        // Basic implementation - in a real application, you would parse dates properly
        if (value == null || filter == null) {
            return true;
        }
        
        String valueStr = value.toString();
        String filterStr = filter.toString();
        
        switch (type) {
            case "equals":
                return valueStr.equals(filterStr);
            case "notEqual":
                return !valueStr.equals(filterStr);
            case "greaterThan":
                return valueStr.compareTo(filterStr) > 0;
            case "lessThan":
                return valueStr.compareTo(filterStr) < 0;
            default:
                return true;
        }
    }
    
    /**
     * Create a comparator based on ag-Grid sort model
     * 
     * @param sortModel The sort model from ag-Grid
     * @param comparators Map of field names to comparators
     * @return Comparator for sorting data
     */
    public java.util.Comparator<T> createSortComparator(Map<String, SortModel> sortModel,
                                                       Map<String, java.util.Comparator<T>> comparators) {
        if (sortModel == null || sortModel.isEmpty() || comparators == null) {
            return null; // No sorting
        }
        
        java.util.Comparator<T> result = null;
        
        for (Map.Entry<String, SortModel> entry : sortModel.entrySet()) {
            String field = entry.getKey();
            SortModel sort = entry.getValue();
            
            if (comparators.containsKey(field)) {
                java.util.Comparator<T> comp = comparators.get(field);
                if ("desc".equals(sort.getSort())) {
                    comp = comp.reversed();
                }
                
                if (result == null) {
                    result = comp;
                } else {
                    result = result.thenComparing(comp);
                }
            }
        }
        
        return result;
    }
}